Skip to content

fix(cursor): seed default cursor with enabled=false to prevent overlay process leak - #1900

Closed
chxrro wants to merge 3 commits into
trycua:mainfrom
chxrro:fix/default-cursor-anonymous-leak-1777
Closed

fix(cursor): seed default cursor with enabled=false to prevent overlay process leak#1900
chxrro wants to merge 3 commits into
trycua:mainfrom
chxrro:fix/default-cursor-anonymous-leak-1777

Conversation

@chxrro

@chxrro chxrro commented Jun 15, 2026

Copy link
Copy Markdown

Problem

Every anonymous (non-session) CUA tool call spawns a cursor overlay process
that is never cleaned up, causing 30+ zombie processes after moderate use.

Root cause: the default cursor is seeded in three places with a hardcoded
assumption that it should always render:

  1. CursorRegistry::new() seeds "default" with enabled: true
  2. overlay::init() seeds default render state from the launch config
  3. RenderStateCore::new() hardcodes visible: true, ignoring
    cfg.enabled entirely — the documented contract says "without a
    session, actions run cursor-less" but the code always renders

The "default" key is explicitly guarded against removal (both in
CursorRegistry::remove() and overlay::apply_msg()), so these
overlays accumulate until daemon restart.

Fix (3 files, 24 lines)

  1. platform-macos/src/cursor/state.rs — seed default cursor
    registry entry with enabled: false
  2. platform-macos/src/cursor/overlay.rs — seed default render
    state with enabled: false
  3. cursor-overlay/src/render_state.rs — derive visible from
    cfg.enabled instead of hardcoding true

The overlay NSWindow still exists (AppKit lifecycle unchanged) but the
default cursor draws nothing — paint_cursor early-returns on
!visible, and animate_cursor_to skips on !cfg.enabled.
Session cursors still work normally via set_agent_cursor_enabled /
start_session.

Verification

  • Updated test: default_cursor_starts_disabled verifies the seed
  • Existing cursor tests pass unchanged for session-based paths
  • Manual: HermesBar + Hermes agent, 20+ anonymous CUA calls → 1 daemon
    process (vs 30+ before)

Closes #1777

Summary by CodeRabbit

Bug Fixes

  • Cursor overlay visibility now properly respects launch-time configuration settings instead of defaulting to always enabled.
  • Default cursor overlay initializes to disabled state for predictable, configuration-based control.
  • Updated tests to verify correct initialization and configuration behavior.

…y leak

The default cursor was seeded with enabled=true in three places:
- CursorRegistry::new() (platform-macos/src/cursor/state.rs)
- overlay::init() (platform-macos/src/cursor/overlay.rs)
- RenderStateCore::new() hardcoded visible=true (cursor-overlay/src/render_state.rs)

This caused every anonymous (non-session) tool call to render a cursor
overlay that was never cleaned up — the "default" key is explicitly
guarded against removal. The documented contract is "without a session,
actions run cursor-less," but the hardcoded visible=true bypassed the
config entirely.

Fixes:
1. state.rs: seed default with enabled=false
2. overlay.rs: seed default render state with enabled=false
3. render_state.rs: derive visible from cfg.enabled instead of hardcoding true

Without a declared session, the overlay window still exists (AppKit
NSWindow lifecycle) but the default cursor draws nothing — paint_cursor
early-returns on !visible, and animate_cursor_to skips on !cfg.enabled.
Session cursors work normally via set_agent_cursor_enabled /
start_session.

Closes #1777
@vercel

vercel Bot commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Someone is attempting to deploy a commit to the Cua Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c5f31d6a-c291-4118-810c-f6ab683b2cf5

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Three files are updated to ensure the seeded "default" anonymous cursor starts disabled: CursorRegistry::new explicitly sets enabled: false for the default entry, overlay.rs init clones the incoming config with enabled forced to false before inserting the default cursor, and RenderStateCore::new derives visible from cfg.enabled instead of hardcoding true. The unit test for the default cursor is updated to match.

Changes

Default cursor disabled at initialization

Layer / File(s) Summary
Disable default cursor in registry, overlay, and render state
libs/cua-driver/rust/crates/platform-macos/src/cursor/state.rs, libs/cua-driver/rust/crates/platform-macos/src/cursor/overlay.rs, libs/cua-driver/rust/crates/cursor-overlay/src/render_state.rs
CursorRegistry::new seeds "default" with enabled: false; overlay init clones the incoming config with enabled forced to false for the "default" entry; RenderStateCore::new sets visible from cfg.enabled instead of true. Unit test updated to assert the default cursor starts disabled and transitions to enabled via set_enabled.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Possibly related PRs

  • trycua/cua#1787: Directly overlaps with cursor-overlay logic that conditionally seeds and animates the session cursor based on enablement, coupling the behavior of cursor visibility initialization between the two PRs.

Poem

🐇 A cursor once blinked on without asking leave,
Now it waits, politely hidden up its sleeve.
enabled: false — a shy little dot,
Until set_enabled wakes it from its cot.
Default is silent, the registry agrees,
No phantom cursor blinking as it please!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: seeding the default cursor with enabled=false to fix the overlay process leak.
Linked Issues check ✅ Passed The PR directly addresses objective 1 from #1777 by implementing cursor session-scoping and preventing anonymous cursor overlay accumulation through the enabled=false seeding approach.
Out of Scope Changes check ✅ Passed All changes are directly related to cursor session-scoping as specified in #1777; no unrelated modifications to other features or systems were introduced.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@libs/cua-driver/rust/crates/cursor-overlay/src/render_state.rs`:
- Around line 99-100: The issue is that visible and cfg.enabled can diverge
after initialization. While RenderStateCore::new initializes visible from
cfg.enabled, the OverlayCommand::SetEnabled handler only mutates visible without
updating cfg.enabled. This causes the animation gating check in
animate_cursor_to (which uses cfg.enabled) to become stale after toggles. Fix
this by ensuring that whenever the enabled state is toggled in the SetEnabled
handler at line 115, both visible and cfg.enabled are updated together to keep
them synchronized.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 19234aa1-ae73-4e06-aee0-9666d9572c4c

📥 Commits

Reviewing files that changed from the base of the PR and between edbcf3b and 547e9da.

📒 Files selected for processing (3)
  • libs/cua-driver/rust/crates/cursor-overlay/src/render_state.rs
  • libs/cua-driver/rust/crates/platform-macos/src/cursor/overlay.rs
  • libs/cua-driver/rust/crates/platform-macos/src/cursor/state.rs

Comment on lines +99 to 100
let visible = cfg.enabled;
Self {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Keep cfg.enabled and visible in sync to avoid stale enablement checks.

RenderStateCore::new now initializes visible from cfg.enabled, but runtime toggles (OverlayCommand::SetEnabled) still mutate only visible. Since animate_cursor_to gates on rs.core.cfg.enabled in platform-macos/src/cursor/overlay.rs (Line 308), enable/disable state can diverge and break animation gating after toggles.

Suggested fix
diff --git a/libs/cua-driver/rust/crates/cursor-overlay/src/render_state.rs b/libs/cua-driver/rust/crates/cursor-overlay/src/render_state.rs
@@
             OverlayCommand::SetEnabled(v) => {
                 self.visible = v;
+                self.cfg.enabled = v;
                 true
             }

Also applies to: 115-115

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@libs/cua-driver/rust/crates/cursor-overlay/src/render_state.rs` around lines
99 - 100, The issue is that visible and cfg.enabled can diverge after
initialization. While RenderStateCore::new initializes visible from cfg.enabled,
the OverlayCommand::SetEnabled handler only mutates visible without updating
cfg.enabled. This causes the animation gating check in animate_cursor_to (which
uses cfg.enabled) to become stale after toggles. Fix this by ensuring that
whenever the enabled state is toggled in the SetEnabled handler at line 115,
both visible and cfg.enabled are updated together to keep them synchronized.

@injaneity

Copy link
Copy Markdown
Collaborator

closing as outdated, as this contradicts the deliberate default-on cursor behavior documented and retained by #1787, while #1779 already merged session-scoped cursor cleanup, crash detection, and resurrection guards

@injaneity injaneity closed this Aug 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

cua-driver-rs: finish the daemon session-identity model (cursor/PiP scoping, config TTL reaper, Win/Linux config parity)

2 participants